package main;
/*
 * CloudStego school project
 * @author lauris kruusamae
 * @author triin uudam
 * @author uku loskit
 */
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;

import javax.imageio.ImageIO;

import org.apache.hadoop.fs.FSDataInputStream;
import org.apache.hadoop.fs.FSDataOutputStream;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;
import org.apache.log4j.Logger;

/**
 * Reducer implementation for message encoding
 * key - base image path
 * value - <byte index, byte value> (message image bytes)
 * 
 * Reads in the base image pixels
 * Writes each message image byte to 3 pixels starting from the given byte index * 3
 * After the message data has been encoded, saves the result image as
 * <base image name>-with-message.png and the encoded message as
 * <base image name>-message.png
 *
 */
class EncodeReduce extends Reducer<Text, Text, IntWritable, IntWritable> {
    
    public static final String KEY_OUTPUT_DIR_PATH = "output_dir_path";
    
    private BufferedImage baseImage;
    private ArrayList<Pixel> basePixels;
    
    private String resultDirPath;
    private String imageName;
    Logger logger = Logger.getLogger(EncodeReduce.class);
    
    /** Reducer setup */
    public void setup (Context context) throws IOException{
    	logger.info("SETUP CALLED");
        resultDirPath = context.getConfiguration().get(KEY_OUTPUT_DIR_PATH);
        if (resultDirPath.isEmpty()) {
            throw new IllegalArgumentException("resultPath cannot be null");
        }
    }

    @Override
    protected void reduce(Text key, Iterable<Text> values, Context context) throws IOException, InterruptedException, NumberFormatException {
    	imageName = Util.getFileName(key.toString());
    	
    	// Read the base image
    	FileSystem fs = FileSystem.get(context.getConfiguration());
    	FSDataInputStream in = fs.open(new Path(key.toString()));
    	baseImage = ImageIO.read(in);
    	in.close();
    	basePixels = Util.getPixelsFromImage(baseImage, true);
    	
    	// Read in dataImageBytes
    	ArrayList<Byte> dataBytes = new ArrayList<Byte>();
    	for (Text text : values) {
    		String[] splittedValues = text.toString().split(",");
    		dataBytes.add(new Byte(Long.parseLong(splittedValues[0]), (byte) Integer.parseInt(splittedValues[1])));
    	}
    	Collections.sort(dataBytes);
    	
    	if (basePixels.size() < dataBytes.size() * 3) {
    		throw new IllegalArgumentException("Data image is too large for base image (" + key.toString() + ")");
    	}
    	
    	Pixel[] pixelHolder = new Pixel[3];
    	int dataCount = dataBytes.size();
    	for (int index = 0; index < dataCount; index++) {
    		Byte value = dataBytes.get(index);
    		
    		pixelHolder[0] = basePixels.get(index * 3);
    		pixelHolder[1] = basePixels.get(index * 3 + 1);
    		pixelHolder[2] = basePixels.get(index * 3 + 2);
    		
    		if (index == (dataCount - 1)) {
    			Util.encodeDataIntoPixels(pixelHolder, value.getValue(), true);
    		} else {
    			Util.encodeDataIntoPixels(pixelHolder, value.getValue(), false);
    		}
    	}
    }
    
    protected void cleanup(Context context) throws IOException ,InterruptedException {
        FileSystem fs = FileSystem.get(context.getConfiguration());
        Path resultImagePath = new Path(resultDirPath + "/" + imageName + "-with-message.png");
        Path messagePath = new Path(resultDirPath + "/" + imageName + "-message.png");
        
        if (fs.exists(resultImagePath)) {
        	fs.delete(resultImagePath, false);
        }
        FSDataOutputStream out = fs.create(resultImagePath);
        ImageIO.write(Util.getImageFromPixels(basePixels, baseImage.getHeight(), baseImage.getWidth()), "png", out);
        out.flush();
        out.close();
        
        if (fs.exists(messagePath)) {
        	fs.delete(messagePath, false);
        }
        
        out = fs.create(messagePath);
        Util.extractMessage(out, basePixels);
        out.flush();
        out.close();
        
        logger.info("CLEANUP CALLED");
    };
}
